#include <algorithm>
#include <iostream> 
#include <assert.h> 
#include "oss_util.h"
#include "google/protobuf/message.h"
#include "google/protobuf/text_format.h"
#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
#include "people.pb.h" 

using namespace google::protobuf;

static const char *OSS_LOGGER = "oss";

namespace {

//
// the twe classes below are copied from protobuf source code:
//     protobuf-2.6.1/src/google/protobuf/text_format.(h/cc)
//
// great thanks to origin authors!!
//

class TextGenerator {
public:
    explicit TextGenerator(io::ZeroCopyOutputStream* output)
        : output_(output),
          buffer_(NULL),
          buffer_size_(0),
          failed_(false)
    {}

    ~TextGenerator() {
        // Only BackUp() if we're sure we've successfully called Next() at least
        // once.
        if (!failed_ && buffer_size_ > 0) {
            output_->BackUp(buffer_size_);
        }
    }

    // Print text to the output stream.
    void Print(const string& str) {
        Write(str.data(), str.size());
    }

    // Print text to the output stream.
    void Print(const char* text) {
        Write(text, strlen(text));
    }

    // True if any write to the underlying stream failed.  (We don't just
    // crash in this case because this is an I/O failure, not a programming
    // error.)
    bool failed() const { return failed_; }

private:
    GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(TextGenerator);

    void Write(const char* data, size_t size) {
        if (failed_) return;
        if (size == 0) return;

        while (size > buffer_size_) {
            // Data exceeds space in the buffer.  Copy what we can and request a
            // new buffer.
            memcpy(buffer_, data, buffer_size_);
            data += buffer_size_;
            size -= buffer_size_;
            void* void_buffer;
            int buf_size = 0;
            failed_ = !output_->Next(&void_buffer, &buf_size);
            buffer_size_ = static_cast<size_t>(buf_size);
            if (failed_) return;
            buffer_ = reinterpret_cast<char*>(void_buffer);
        }

        // Buffer is big enough to receive the data; copy it.
        memcpy(buffer_, data, size);
        buffer_ += size;
        buffer_size_ -= size;
    }

    io::ZeroCopyOutputStream* output_;
    char* buffer_;
    size_t buffer_size_;
    bool failed_;
};

class FieldValuePrinter {
public:
    FieldValuePrinter() {}
    ~FieldValuePrinter() {}

    std::string PrintBool(bool val) const {
        return val ? "true" : "false";
    }

    std::string PrintInt32(int32 val) const {
        return std::to_string(val);
    }

    std::string PrintUInt32(uint32 val) const {
        return std::to_string(val);
    }

    std::string PrintInt64(int64 val) const {
        return std::to_string(val);
    }

    std::string PrintUInt64(uint64 val) const {
        return std::to_string(val);
    }

    std::string PrintFloat(float val) const {
        return std::to_string(val);
    }

    std::string PrintDouble(double val) const {
        return std::to_string(val);
    }

    std::string PrintString(const std::string& val) const {
        return val;
    }

    std::string PrintBytes(const std::string& val) const {
        return PrintString(val);
    }

    std::string PrintEnum(int32 val, const string&) const {
        return PrintInt32(val);
    }

    // std::string PrintFieldName(const Message& message,
    //                            const Reflection* reflection,
    //                            const FieldDescriptor* field) const {
    //     sk_assert(0);
    // }

    // std::string PrintMessageStart(const Message& message,
    //                               int field_index,
    //                               int field_count,
    //                               bool single_line_mode) const {
    //     sk_assert(0);
    // }
    // std::string PrintMessageEnd(const Message& message,
    //                             int field_index,
    //                             int field_count,
    //                             bool single_line_mode) const {
    //     sk_assert(0);
    // }

private:
    GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(FieldValuePrinter);
};

void print(const Message& msg, TextGenerator& generator, bool top_level = false);

void print_field_value(const Message& msg,
                       const Reflection *reflection,
                       const FieldDescriptor *field,
                       int index,
                       TextGenerator& generator) {
    assert(field->is_repeated() || index == -1);

    FieldValuePrinter printer;
    if (field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE)
        generator.Print("|");

    switch (field->cpp_type()) {
#define OUTPUT_FIELD(CPPTYPE, METHOD)                                   \
    case FieldDescriptor::CPPTYPE_##CPPTYPE:                            \
      generator.Print(printer.Print##METHOD(field->is_repeated()       \
               ? reflection->GetRepeated##METHOD(msg, field, index) \
               : reflection->Get##METHOD(msg, field)));             \
        break

    OUTPUT_FIELD( INT32,  Int32);
    OUTPUT_FIELD( INT64,  Int64);
    OUTPUT_FIELD(UINT32, UInt32);
    OUTPUT_FIELD(UINT64, UInt64);
    OUTPUT_FIELD( FLOAT,  Float);
    OUTPUT_FIELD(DOUBLE, Double);
    OUTPUT_FIELD(  BOOL,   Bool);
#undef OUTPUT_FIELD

    case FieldDescriptor::CPPTYPE_STRING: {
        std::string scratch;
        const std::string& value = field->is_repeated()
                ? reflection->GetRepeatedStringReference(msg, field, index, &scratch)
                : reflection->GetStringReference(msg, field, &scratch);
        if (field->type() == FieldDescriptor::TYPE_STRING)
            generator.Print(printer.PrintString(value));
        else {
            assert(field->type() == FieldDescriptor::TYPE_BYTES);
            generator.Print(printer.PrintBytes(value));
        }
        break;
    }

    case FieldDescriptor::CPPTYPE_ENUM: {
        const EnumValueDescriptor *desc = field->is_repeated()
                ? reflection->GetRepeatedEnum(msg, field, index)
                : reflection->GetEnum(msg, field);
        generator.Print(printer.PrintEnum(desc->number(), desc->name()));
        break;
    }

    case FieldDescriptor::CPPTYPE_MESSAGE: {
        print(field->is_repeated()
              ? reflection->GetRepeatedMessage(msg, field, index)
              : reflection->GetMessage(msg, field), generator);
        break;
    }

    default:
        printf("line %d error\n", __LINE__);
    }
}

void print_short_repeated_field(const Message& msg,
                                const Reflection *reflection,
                                const FieldDescriptor *field,
                                TextGenerator& generator) {
    int size = reflection->FieldSize(msg, field);
    for (int i = 0; i < size; ++i)
        print_field_value(msg, reflection, field, i, generator);
}

void print_field(const Message& msg,
                 const Reflection *reflection,
                 const FieldDescriptor *field,
                 TextGenerator& generator) {
    if (field->is_repeated() &&
        field->cpp_type() != FieldDescriptor::CPPTYPE_STRING &&
        field->cpp_type() != FieldDescriptor::CPPTYPE_MESSAGE) {
        print_short_repeated_field(msg, reflection, field, generator);
        return;
    }

    int count = 0;
    if (field->is_repeated())
        count = reflection->FieldSize(msg, field);
    else if (reflection->HasField(msg, field))
        count = 1;
    // TODO: how to handle those fields which are not set??

    for (int i = 0; i < count; ++i) {
        const int index = field->is_repeated() ? i : -1;
        print_field_value(msg, reflection, field, index, generator);
    }
}

void print(const Message& msg, TextGenerator& generator, bool top_level) {
    const Reflection *reflection = msg.GetReflection();
    std::vector<const FieldDescriptor*> fields;
    reflection->ListFields(msg, &fields);
    std::sort(fields.begin(), fields.end(),
              [](const FieldDescriptor* l, const FieldDescriptor *r) {
        return l->index() < r->index();
    });

    if (top_level)
        generator.Print(msg.GetDescriptor()->name());

    for (size_t i = 0; i < fields.size(); ++i)
        print_field(msg, reflection, fields[i], generator);
}

} // namespace

static bool oss_msg2txt(const Message& msg, const std::string **output) {
    static std::string buf;

    buf.clear();
    io::StringOutputStream stream(&buf);
    TextGenerator generator(&stream);

    print(msg, generator, true);
    if (generator.failed())
        return false;

    *output = &buf;
    return true;
}

void oss_log(const Message& msg) {
    const std::string *txt = nullptr;
    bool ok = oss_msg2txt(msg, &txt);
    if (!ok) {
        printf("oss_msg2txt(...) error.");
        return;
    }

    printf("%s\n", txt->c_str());
}

int main(int argc, char **argv) {
    PeopleList pls;
    pls.set_num(1);
    //pls.set_content("xxxxx");
    pls.set_a(2);
    auto p1 = pls.add_pl();
    p1->set_name("");
    p1->set_id(1);
    p1->set_email("mail1");

    auto p2 = pls.add_pl();
    p2->set_name("p2");
    p2->set_id(2);
    //p2->set_email("mail1");

    const std::string *output = NULL;
    bool ok = oss_msg2txt(pls, &output);
    if (!ok) std::cout << "not ok" << std::endl;
    else std::cout << *output << std::endl;

    return 0;
}

// test program as below

#if 0

// test.proto
message parent {
    message child {
        optional int32 age   = 1;
        repeated string toys = 2;
    }

    optional int32 gender   = 1;
    repeated child children = 2;
}

// test.cpp
#include <iostream>
#include <google/protobuf/message.h>
#include "test.pb.h"
#include "oss_util.h"

int main() {
    parent msg;
    msg.set_gender(1);

    parent::child *c0 = msg.add_children();
    c0->set_age(17);
    c0->add_toys("gun");
    c0->add_toys("mobile");

    parent::child *c1 = msg.add_children();
    c1->set_age(27);
    c1->add_toys("keyboard");
    c1->add_toys("computer");

    const std::string *output = NULL;
    bool ok = oss_msg2txt(msg, &output);
    if (!ok) std::cout << "not ok" << std::endl;
    else std::cout << *output << std::endl;

    return 0;
}

#endif
